Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Displaying Raw • View renderedDownload


docs/en/plugins.md v4.8.1 (f6cc7b83) Text, 9.42 KB

Tc9d1d9# Plugins

Plugins extend MeshChatX with extra tools, nav items, and background behaviour. They are capability-gated: a plugin only gets what you grant at install time.

Manage them from **Settings → Plugins**. Disable every packaged plugin at startup with Ta5d6ff`--disable-plugins` or Ta5d6ff`MESHCHAT_DISABLE_PLUGINS=true`.

Tc9d1d9## What plugins can do

Tff7b72- Add a row on the **Tools** page
Tff7b72- Add an item in the main **Navigation** sidebar
Tff7b72- React to mesh events (announces, RNS link traffic)
Tff7b72- Call narrowly declared backend managers (path table, debug log, bug reports, RNS links)
Tff7b72- Keep a private key-value store (Ta5d6ff`storage: isolated`)
Tff7b72- Optionally fetch clearnet HTTP (Ta5d6ff`network: fetch`), still subject to **Privacy mode**

Plugins cannot rewrite core MeshChatX. They do not get open-ended filesystem or process control unless you opt into Sideband Python plugins (see below).

Tc9d1d9## Runtimes

| Runtime | Where it runs | Trust level |
| --------------- | ------------------------- | ----------------------------------------------- |
| Frontend JS | Browser Web Worker | Medium. Sandboxed worker, capability grants |
| Backend WASM | Ta5d6ff`wasmtime` on the server | Medium. Fuel-metered, capability-gated host |
| Backend Python | In-process with MeshChatX | High. Permission-checked host, still powerful |
| Sideband Ta5d6ff`*.py` | In-process, flat files | Highest. Opt-in danger switch, full host access |

A packaged plugin can ship frontend only, backend only, or both.

Tc9d1d9## Install flow

Ta5d6ff```
Pick ZIP or .wasm file in Settings → Plugins
|
--> Preview (permissions, URLs, signature, findings)
|
--> You grant or deny each capability
|
--> Optional: trust a valid signer
|
--> Install + integrity hash stored
|
--> Enable
|
--> Frontend Worker loads (if present)
--> Backend WASM / Python activates (if present)
```

Invalid signatures hard-block install. Unsigned packages are allowed. Present-but-broken signatures are not.

After install, MeshChatX hashes the on-disk tree. If files change outside the app, the plugin is auto-disabled as tampered.

Tc9d1d9## Bundled example: Bug Reports

Ta5d6ff`com.meshchatx.mcx-bugs` ships with MeshChatX. It adds a **Bug Reports** tool for sending redacted debug logs to an Ta5d6ff`mcx-bugs-v1` collector, or running a collector yourself.

Layout:

Ta5d6ff```
mcx-bugs/
plugin.json
frontend/main.js
backend/main.py
locales/en.json
```

Use it as the reference package when building your own.

Tc9d1d9## Manifest (`plugin.json`)

Every packaged plugin needs a root Ta5d6ff`plugin.json`.

Ta5d6ff```Ta5d6ffjson
Tb4b4b4{
Tff7b72"id"Tb4b4b4: Ta5d6ff"com.example.my-plugin"Tb4b4b4,
Tff7b72"version"Tb4b4b4: Ta5d6ff"1.0.0"Tb4b4b4,
Tff7b72"apiVersion"Tb4b4b4: T79c0ff1Tb4b4b4,
Tff7b72"name"Tb4b4b4: Ta5d6ff"My Plugin"Tb4b4b4,
Tff7b72"description"Tb4b4b4: Ta5d6ff"Adds a custom tool."Tb4b4b4,
Tff7b72"frontend"Tb4b4b4: Tb4b4b4{
Tff7b72"entry"Tb4b4b4: Ta5d6ff"frontend/main.js"Tb4b4b4,
Tff7b72"type"Tb4b4b4: Ta5d6ff"js"
Tb4b4b4},
Tff7b72"backend"Tb4b4b4: Tb4b4b4{
Tff7b72"entry"Tb4b4b4: Ta5d6ff"backend/main.py"Tb4b4b4,
Tff7b72"type"Tb4b4b4: Ta5d6ff"python"
Tb4b4b4},
Tff7b72"i18n"Tb4b4b4: Tb4b4b4{
Tff7b72"directory"Tb4b4b4: Ta5d6ff"locales"Tb4b4b4,
Tff7b72"defaultLocale"Tb4b4b4: Ta5d6ff"en"
Tb4b4b4},
Tff7b72"contributes"Tb4b4b4: Tb4b4b4{
Tff7b72"navItems"Tb4b4b4: Tb4b4b4[
Tb4b4b4{
Tff7b72"id"Tb4b4b4: Ta5d6ff"my-plugin"Tb4b4b4,
Tff7b72"route"Tb4b4b4: Tb4b4b4{ Tff7b72"name"Tb4b4b4: Ta5d6ff"plugin-my-plugin" Tb4b4b4},
Tff7b72"icon"Tb4b4b4: Ta5d6ff"puzzle"Tb4b4b4,
Tff7b72"labelKey"Tb4b4b4: Ta5d6ff"nav"
Tb4b4b4}
Tb4b4b4],
Tff7b72"toolsPageEntries"Tb4b4b4: Tb4b4b4[
Tb4b4b4{
Tff7b72"name"Tb4b4b4: Ta5d6ff"my-plugin"Tb4b4b4,
Tff7b72"route"Tb4b4b4: Tb4b4b4{ Tff7b72"name"Tb4b4b4: Ta5d6ff"plugin-my-plugin" Tb4b4b4},
Tff7b72"icon"Tb4b4b4: Ta5d6ff"puzzle"Tb4b4b4,
Tff7b72"titleKey"Tb4b4b4: Ta5d6ff"title"Tb4b4b4,
Tff7b72"descriptionKey"Tb4b4b4: Ta5d6ff"description"
Tb4b4b4}
Tb4b4b4]
Tb4b4b4},
Tff7b72"permissions"Tb4b4b4: Tb4b4b4{
Tff7b72"hooks"Tb4b4b4: Tb4b4b4[Ta5d6ff"announce.received"Tb4b4b4],
Tff7b72"managers"Tb4b4b4: Tb4b4b4[Ta5d6ff"destinationPath.read"Tb4b4b4],
Tff7b72"storage"Tb4b4b4: Ta5d6ff"isolated"Tb4b4b4,
Tff7b72"network"Tb4b4b4: Ta5d6ff"none"
Tb4b4b4}
Tb4b4b4}
Ta5d6ff```

Notes:

Tff7b72- Ta5d6ff`id` is reverse-DNS style and must stay stable across versions
Tff7b72- Ta5d6ff`apiVersion` is currently Ta5d6ff`1`
Tff7b72- Plugin strings live in the plugin bundle (Ta5d6ff`locales/{locale}.json`), not core Ta5d6ff`en.json`
Tff7b72- Ta5d6ff`contributes` wires UI slots through the frontend registries

Tc9d1d9## Permissions

Nothing is available unless it is declared in the manifest and granted in the install dialog.

Tc9d1d9### Hooks

| Hook | When it fires |
| ------------------- | ----------------------------------------------------------- |
| Ta5d6ff`announce.received` | A Reticulum announce arrives |
| Ta5d6ff`rns.link.event` | Generic RNS Link traffic (Ta5d6ff`packet_received`, Ta5d6ff`link_closed`) |

Hook events reach the UI as WebSocket Ta5d6ff`plugin.event` frames, then into the plugin Worker.

Tc9d1d9### Managers

| Manager | Purpose |
| ---------------------- | ----------------------------- |
| Ta5d6ff`destinationPath.read` | Read the Reticulum path table |
| Ta5d6ff`debugLog.read` | Read redacted debug logs |
| Ta5d6ff`bugReport.*` | Bug report / collector APIs |
| Ta5d6ff`rnsLink.open` | Open or reuse an RNS link |
| Ta5d6ff`rnsLink.identify` | Identify on a cached link |
| Ta5d6ff`rnsLink.request` | Request/response on a link |
| Ta5d6ff`rnsLink.send` | Send a raw link packet |
| Ta5d6ff`rnsLink.close` | Tear down a cached link |

Call managers from a plugin with Ta5d6ff`POST /api/v1/plugins/{id}/invoke` and Ta5d6ff`method: "callManager"`. Details for the link transport are in [Tff7b72RNS Link API](Te6edf3rns-link-api.md).

Tc9d1d9### Storage and network

| Permission | Effect |
| ------------------- | --------------------------------------------------------------------- |
| Ta5d6ff`storage: isolated` | Private key-value store in the MeshChatX database |
| Ta5d6ff`storage: none` | No plugin storage |
| Ta5d6ff`network: fetch` | Outbound HTTP allowed (still blocked by Privacy mode when that is on) |
| Ta5d6ff`network: none` | No clearnet fetch |

Install preview also scans plugin files for external Ta5d6ff`http://` / Ta5d6ff`https://` URLs and shows them before you grant network access.

Tc9d1d9## How a frontend plugin runs

Ta5d6ff```
Settings enable plugin
|
--> PluginHost loads /api/v1/plugins
|
--> Fetch frontend entry as text
|
--> Spawn pluginWorker.js (module Worker)
|
--> Register nav / tools contributions
|
--> Subscribe to plugin.event on /ws (if hooks granted)
|
--> Worker may invoke backend via /api/v1/plugins/{id}/invoke
```

The Worker talks to the host with typed messages (Ta5d6ff`init`, Ta5d6ff`event`, Ta5d6ff`request`). The host never gives the Worker a raw privileged API.

Tc9d1d9## How a backend plugin runs

Ta5d6ff```
Enable plugin
|
+--> type: wasm --> load into wasmtime, fuel + host caps
|
+--> type: python --> import entry, call activate(host)
|
--> Hooks fan out from PluginManager
|
--> invoke(method, args) for RPC from the UI Worker
```

Python host surface (permission-checked):

Tff7b72- Ta5d6ff`host.log(message)`
Tff7b72- Ta5d6ff`host.call_manager(capability, args)`
Tff7b72- Ta5d6ff`host.storage_get(key)` / Ta5d6ff`host.storage_set(key, value)`
Tff7b72- Ta5d6ff`host.network_fetch_allowed()`

Tc9d1d9## Packaging and signing

Distribute as:

Tff7b721. **ZIP** with Ta5d6ff`plugin.json` and assets
Tff7b722. **WASM bundle** (single Ta5d6ff`.wasm` with embedded manifest / files / optional signature)

Signature file for ZIP/dir packages: Ta5d6ff`meshchatx.plugin.rsg`

WASM custom sections:

Ta5d6ff```
meshchatx.plugin --> embedded plugin.json
meshchatx.files --> embedded text assets
meshchatx.signature --> RSG over payload without this section
```

Canonical ZIP signing uses sorted paths and fixed 1980-01-01 mtimes. The signature file itself is excluded from the signed payload.

Sign and verify with:

Ta5d6ff```Ta5d6ffbash
python3 scripts/sign-plugin.py sign-dir ./my-plugin --identity <rnid>
python3 scripts/sign-plugin.py verify-dir ./my-plugin
python3 scripts/sign-plugin.py sign-zip ./my-plugin.zip --identity <rnid>
python3 scripts/sign-plugin.py sign-wasm ./plugin.wasm --identity <rnid>
python3 scripts/sign-plugin.py sign-py ./legacy_plugin.py --identity <rnid>
Ta5d6ff```

Trust status in the UI:

Ta5d6ff```
No .rsg present
--> Unsigned (install allowed)

Valid .rsg, signer unknown
--> Signed (you can add to Trusted Publishers)

Valid .rsg, signer in Trusted Publishers
--> Trusted

Broken / mismatched .rsg
--> Invalid (install blocked)
```

Tc9d1d9## Sideband-compatible plugins

Legacy Sideband-style flat Ta5d6ff`*.py` files are separate from packaged ZIP/WASM plugins.

Ta5d6ff```
Settings → Plugins → Sideband
|
--> Confirm danger prompt
|
--> Set directory of *.py files
|
--> Optional filename.py.rsg next to each script
|
--> Reload
```

These run in-process with full host access. They are not ZIP-permission gated. Keep the master switch off unless you trust every file in that directory.

Tc9d1d9## Operator tips

Tff7b72- Prefer signed packages from publishers you added yourself
Tff7b72- Deny Ta5d6ff`network: fetch` unless the plugin truly needs clearnet
Tff7b72- Prefer WASM backends over Python when you can
Tff7b72- Use Ta5d6ff`--disable-plugins` when diagnosing weird UI or backend behaviour
Tff7b72- Treat Sideband plugins like running arbitrary local scripts

Tc9d1d9## See also

Tff7b72- [Tff7b72Tools and utilities](Te6edf3tools.md) for the Tools page and contribution overview
Tff7b72- [Tff7b72RNS Link API](Te6edf3rns-link-api.md) for Ta5d6ff`rnsLink.*` and Ta5d6ff`rns.link.event`
Tff7b72- [Tff7b72Architecture and design](Te6edf3architecture.md) for the plugin runtime overview
Tff7b72- [Tff7b72Identities, privacy, and security](Te6edf3identity-and-security.md) for signing and Privacy mode


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────